<?php

class ArrayProcessor_XMLElements  {
	
	/**
	 * These constants are the elements used within the xml
	 */
	const ELE_GENERAL = 'general';
	const ELE_STRUCTURE = 'structure';
	const ELE_ATTRIBUTES = 'attribute';
	const ELE_ELEMENT = 'element';
	
	const ARRAY_ELE_CONTENT = 'content';
	
	/**
	 * 
	 * Extracts the given string out of a XML-Structure and returns it in an array
	 * Example Structure:
	 * <structure>
	 * 	<element>
	 * 		<a>GOTO
	 * 			<img src="www.google.de"></img>
	 * 		</a>
	 * 		<a>TEST</a>
	 * 	</element>
	 * </structure>
	 * @param DOMDocument $xmlStructure
	 * @param String $nodeName
	 */
	public static function XMLToHTMLArray ($xmlStructure, $nodeName, $limiter)  {
		
		$rootChildren = $xmlStructure->getElementsByTagName($nodeName);
		
		if ($rootChildren->length == 0)  {
			return;
		}
		
		$xmlStructureArray = array();
		
		
// Travers through all elements $parent is root for in this example <structure>
// occurs only one time
		foreach ($rootChildren as $child)  {
			$childTravers = $child->firstChild;
			
// loop over all nodes that $child is parent for in this example <element>
			do  {
				$children = $childTravers->firstChild;

				if ($childTravers->hasChildNodes() && ($children->nodeType != XML_TEXT_NODE || $children->nodeType == XML_TEXT_NODE && !is_null($children->nextSibling->nodeType)))  {
					$attributes = self::getNodeAttributes($childTravers);
					$attributes = array_merge($attributes, self::recursiveWalk($childTravers));
					$xmlStructureArray = self::sameNodeName($xmlStructureArray, $attributes, $childTravers);
				}
				else  {
					$attributes = self::getNodeAttributes($childTravers);
					$trimmed = trim($childTravers->nodeValue);
					if (empty($trimmed))  {
						$xmlStructureArray = self::sameNodeName($xmlStructureArray, $attributes, $childTravers);
					}
					else  {
						$content[self::ARRAY_ELE_CONTENT] = $trimmed;
						$merged = array_merge($attributes, $content);
						$xmlStructureArray = self::sameNodeName($xmlStructureArray, $merged, $childTravers);
					}
				}
			}
			while ($childTravers = $childTravers->nextSibling);
			
		}

		return $xmlStructureArray;
	}
	
	/**
	 * 
	 * Takes all children of $parent and merge them recursive in an array
	 * @param unknown_type $parent
	 */
	private static function recursiveWalk ($parent)  {

// holds the complete xml structure as a multidimensional array
		$xmlStructureArray = null;
		
		$child = $parent->firstChild;
		
// loop over all siblings of $child (<element>) in this example <a>, <img>
		do  {

// check whether a child is parent for other nodes if yes go into the recursion
// content like <a>TEST</a> is also handled as a node
			if ($child->hasChildNodes())  {
				$children = self::recursiveWalk($child);
			}
			
// collect all attributes of a node
			$attributes = self::getNodeAttributes($child);

			
			$trimmed = trim($child->nodeValue);
			
// if no children has been passed over by the recursion you have reached the deepest level
// only content can be passed back (!! Important content ist treaten as node !!)
			if (!empty($children))  {
				
// if a children just containing the 'content' node append it directly to the array as shown:
// 'a' => 'GOTO'
				if (array_key_exists(self::ARRAY_ELE_CONTENT, $children) && empty($attributes) && count($children) == 1)  {
					$xmlStructureArray = self::sameNodeName($xmlStructureArray, $children[self::ARRAY_ELE_CONTENT], $child);
				}
				else  {
					$merged = array_merge($attributes, $children);
					$xmlStructureArray = self::sameNodeName($xmlStructureArray, $merged, $child);
				}
				unset ($children);
			}
			
// if there is no content nothing has to be passed back. $xmlStructureArray is left empty
			else if (!empty($trimmed)) {
				$xmlStructureArray[self::ARRAY_ELE_CONTENT] = $child->nodeValue;
			}
			
			else  {
				$xmlStructureArray = self::sameNodeName($xmlStructureArray, $attributes, $child);
			}
		}
		while ($child = $child->nextSibling);

		return $xmlStructureArray;
	}
	
	private static function getNodeAttributes ($node)  {
		$attributes = array();
		if ($node->hasAttributes())  {
			foreach ($node->attributes as $attribute)  {
				$attributes[$attribute->nodeName] = $attribute->nodeValue;
			}
		}
		return $attributes;
	}
	
	/**
	 * 
	 * This function checks if the current processed node is already in $xmlStructureArray
	 * Example:
	 * Array ( 'div' => array('content' => 'Hallo'))
	 * Array ( 'div' => array('content' => 'TEST'))
	 * 
	 * Result:
	 * Array ( 'div' => array('0' => array('content' => 'Hallo')
	 * 						  '1' => array('content' => 'TEST')))
	 * @param Array $xmlStructureArray
	 * @param Array $currentStructure
	 * @param DOMNode $node
	 */
	private static function sameNodeName ($xmlStructureArray, $currentStructure, $node)  {
		
// If the $node is already in $xmlStructureArray extract this part and delete it from $xmlStructureArray
		if (is_array($xmlStructureArray) && array_key_exists($node->nodeName, $xmlStructureArray))  {
			$allAttributes[] = $xmlStructureArray[$node->nodeName];
			unset($xmlStructureArray[$node->nodeName]);
			
// If the Result: of the example would be in $xmlStructureArray it would be multidimensional
// because of this $currentStructure has to be inserted on level deeper		
			if (isset($allAttributes[0][1]) && is_array($allAttributes[0][1]))  {
				$allAttributes[0][] = $currentStructure;
				$xmlStructureArray[$node->nodeName] = $allAttributes[0];
			}
			
// If it looks like in the example it can be inserted directly
			else  {
				$allAttributes[] = $currentStructure;
				$xmlStructureArray[$node->nodeName] = $allAttributes;
			}
			
// Afterwards $allAttributes have to be cleared
			$allAttributes = array();
		}
		
// If the current processed node is not in $xmlStructureArray it can be inserted directly
		else  {
			$xmlStructureArray[$node->nodeName] = $currentStructure;
		}
		
		return $xmlStructureArray;
	}
	
	/**
	 * 
	 * Removes the whitespace in XML structures in order to avoid wrong extracts
	 * @param String $file
	 */
	public static function removeWhitespace ($file)  {
		$XmlFile = fopen($file, 'r');
		$XmlFileText = fread($XmlFile, filesize($file));
		fclose($XmlFile);
		
// Replace the whitespace between nodes and between characters and nodes
		$regex[0] = '/>\s+</';
		$regex[1] = '/(\w+)\s+</';
		$replace[0] = '><';
		$replace[1] = '$1<';
		return preg_replace($regex, $replace, $XmlFileText);
	}
}
